use crate::class_loader::ClassLoader;
use crate::heap::Heap;
use crate::registry::Registry;
use crate::thread::Thread;

pub struct VirtualMachine {
    class_loader: ClassLoader,
    main_class: String,
    registry: Registry,
    heap: Heap,
    initialization_queue: Vec<String>,
}

impl VirtualMachine {
    pub fn new(class_path: &str, main_class: &str) -> VirtualMachine {
        VirtualMachine {
            class_loader: ClassLoader::new(class_path),
            main_class: main_class.replace(".", "/"),
            registry: Registry::default(),
            heap: Heap::default(),
            initialization_queue: vec![],
        }
    }

    pub fn run(&mut self, _args: &[&str]) {
        self.eager_load_recursively(self.main_class.to_string());
        self.initialize();

        let main_thread = Thread::new(
            &self.main_class,
            "main:([Ljava/lang/String;)V",
            &self.registry,
            self.heap.clone(),
        );
        main_thread.run();
    }

    fn eager_load_recursively(&mut self, class_name: String) {
        if class_name == "java/lang/Object" {
            return; // TODO should be loaded from rt.jar before
        }

        if self.registry.classes.contains_key(&*class_name) {
            return; // TODO hide into registry
        }

        let class = self.class_loader.find_class(&*class_name);
        let class_name = class.this_class.to_string();

        self.eager_load_recursively(class.super_class.to_string());
        self.registry.add_class(class);
        self.initialization_queue.push(class_name.to_string());

        let class = self.registry.class(&*class_name);
        let classes_names = class.constant_pool.classes();

        for class_name in classes_names {
            self.eager_load_recursively(class_name);
        }
    }

    pub fn initialize(&mut self) {
        for class_name in &self.initialization_queue {
            if let Some(_) = self.registry.class(class_name).method("<clinit>:()V") {
                Thread::new(
                    class_name,
                    "<clinit>:()V",
                    &self.registry,
                    self.heap.clone(),
                )
                .run();
            }
        }
    }
}
